package com.winsdom.service.customer.impl;

import com.winsdom.entity.customer.CustomerPersonalUpdateRequest;
import com.winsdom.mapper.customer.CustomerPersonalUpdateMapper;
import com.winsdom.service.customer.CustomerPersonalUpdateService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.LinkedHashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 个人客户信息更新服务实现类
 * <p>
 * 负责处理个人客户信息的更新业务逻辑，包括：
 * 1. 客户类型校验（强制为个人客户）
 * 2. 客户主表字段更新（仅变更字段）
 * 3. 个人扩展信息（证件类型、号码）更新
 * 4. 证件图片强制更新
 * 5. 联系人信息全量更新（先删除旧数据，再插入新数据）
 * <p>
 * 所有操作通过@Transactional注解保证事务一致性，
 * 若中途出现异常将自动回滚，确保数据完整性。
 */
@Service
@Transactional
public class CustomerPersonalUpdateServiceImpl implements CustomerPersonalUpdateService {
    private static final Logger log = LoggerFactory.getLogger(CustomerPersonalUpdateServiceImpl.class);

    @Resource
    private CustomerPersonalUpdateMapper mapper;

    /**
     * 更新个人客户信息的完整业务流程
     * <p>
     * 执行顺序：
     * 1. 校验客户类型是否为个人（类型码3），否则抛出异常
     * 2. 查询客户现有数据（用于字段变更对比）
     * 3. 更新客户主表中发生变更的字段
     * 4. 更新个人扩展表中发生变更的字段（证件类型、号码）
     * 5. 强制更新证件图片信息（如有新路径）
     * 6. 全量更新联系人及楼盘关联（先删除旧数据，再插入去重后的新数据）
     *
     * @param request 个人客户更新请求实体，包含更新后的客户信息
     * @throws IllegalArgumentException 当客户类型不为个人或客户ID不存在时抛出
     */
    @Override
    public void update(CustomerPersonalUpdateRequest request) {
        // 1. 校验客户类型（业务规则：个人客户类型码固定为3）
        if (!request.getCustomerType().equals(3)) {
            throw new IllegalArgumentException("客户类型必须为个人（类型码：3），当前类型码：" + request.getCustomerType());
        }

        // 2. 查询客户现有数据（包含主表和扩展表信息）
        Map<String, Object> oldData = mapper.selectCustomerData(request.getCustomerId());
        if (oldData.isEmpty()) {
            throw new IllegalArgumentException("未找到客户ID：" + request.getCustomerId() + " 的记录");
        }

        // 3. 更新客户主表字段（仅更新有变更的字段）
        updateCustomerMainFields(request, oldData);

        // 4. 更新个人扩展表字段（证件类型、证件号码）
        updatePersonalFields(request, oldData);

        // 5. 更新证件图片信息（强制覆盖，无记录则忽略）
        updateIdImage(request);

        // 6. 全量更新联系人及楼盘关联（先删除旧数据，再插入新数据）
        processContacts(request);
    }

    /**
     * 更新客户主表中发生变更的字段（姓名、手机、邮箱）
     * <p>
     * 对比请求数据与现有数据，仅当字段值不同时执行更新，减少无效操作
     *
     * @param request  更新请求对象，包含新数据
     * @param oldData  客户现有数据（从数据库查询）
     */
    private void updateCustomerMainFields(CustomerPersonalUpdateRequest request, Map<String, Object> oldData) {
        boolean isUpdateNeeded = false;
        // 客户姓名
        isUpdateNeeded |= isFieldModified(oldData, "customerName", request.getCustomerName());
        // 手机号码
        isUpdateNeeded |= isFieldModified(oldData, "mobilePhone", request.getMobilePhone());
        // 电子邮箱
        isUpdateNeeded |= isFieldModified(oldData, "email", request.getEmail());

        if (isUpdateNeeded) {
            mapper.updateCustomerMain(request);
            log.debug("客户主表字段更新，客户ID：{}", request.getCustomerId());
        }
    }

    /**
     * 更新个人扩展表字段（证件类型、证件号码）
     * <p>
     * 对比请求数据与现有数据，仅当字段值不同时执行更新
     *
     * @param request  更新请求对象，包含新数据
     * @param oldData  客户现有数据（从数据库查询）
     */
    private void updatePersonalFields(CustomerPersonalUpdateRequest request, Map<String, Object> oldData) {
        boolean isUpdateNeeded = false;
        // 证件类型
        isUpdateNeeded |= isFieldModified(oldData, "idType", request.getIdType());
        // 证件号码
        isUpdateNeeded |= isFieldModified(oldData, "idNumber", request.getIdNumber());

        if (isUpdateNeeded) {
            mapper.updatePersonalInfo(request);
            log.debug("个人扩展表字段更新，客户ID：{}", request.getCustomerId());
        }
    }

    /**
     * 强制更新证件图片路径（覆盖式更新）
     * <p>
     * 若请求中提供了新的图片路径，直接更新数据库记录，
     * 无论原有记录是否存在（由Mapper实现具体逻辑）
     *
     * @param request 更新请求对象，包含新的证件图片路径
     */
    private void updateIdImage(CustomerPersonalUpdateRequest request) {
        if (request.getIdImagePath() != null) {
            mapper.forceUpdateIdImage(request);
            log.debug("证件图片更新，客户ID：{}", request.getCustomerId());
        }
    }

    /**
     * 全量更新联系人及楼盘分类关联关系
     * <p>
     * 逻辑说明：
     * 1. 先删除该客户的所有旧联系人及关联记录（级联删除）
     * 2. 遍历新联系人列表，插入新的联系人信息
     * 3. 对每个联系人的楼盘名称列表进行去重（保持顺序）
     * 4. 根据去重后的楼盘名称查询分类ID，插入关联关系
     *
     * @param request 更新请求对象，包含新的联系人信息
     */
    private void processContacts(CustomerPersonalUpdateRequest request) {
        Long customerId = request.getCustomerId();
        // 1. 删除旧联系人及关联记录（确保数据干净）
        mapper.deleteOldContacts(customerId);
        log.debug("删除旧联系人，客户ID：{}", customerId);

        if (request.getContactInfos() != null) {
            for (CustomerPersonalUpdateRequest.ContactInfo contact : request.getContactInfos()) {
                // 关键修复：设置联系人所属客户ID（避免外键关联错误）
                contact.setCustomerId(customerId);
                // 插入新联系人
                mapper.insertContact(contact);
                Long contactId = contact.getContactId();

                // 2. 对楼盘名称去重（使用LinkedHashSet保持用户输入顺序）
                Set<String> uniqueProperties = new LinkedHashSet<>(contact.getReceiveProperties());

                // 3. 建立联系人与楼盘分类的关联关系
                uniqueProperties.forEach(propertyName -> {
                    Long categoryId = mapper.selectCategoryIdByName(propertyName);
                    if (categoryId != null) {
                        mapper.insertContactProperty(contactId, categoryId);
                        log.debug("联系人ID {} 关联楼盘分类ID {}", contactId, categoryId);
                    } else {
                        log.warn("未找到楼盘分类名称 [{}] 对应的ID，联系人ID：{}", propertyName, contactId);
                    }
                });
            }
        }
    }

    /**
     * 判断字段值是否发生变更（支持null值安全对比）
     *
     * @param oldData   旧数据Map（键为字段名，值为旧值）
     * @param field     字段名（如"customerName"）
     * @param newValue  新值
     * @return 若新值非空且与旧值不同，则返回true，否则返回false
     */
    private boolean isFieldModified(Map<String, Object> oldData, String field, Object newValue) {
        Object oldValue = oldData.get(field);
        // 使用Objects.equals处理null值对比，避免空指针异常
        return newValue != null && !Objects.equals(oldValue, newValue);
    }
}